/*
 * Copyright (c) 2007 innoSysTec (R) GmbH, Germany. All rights reserved.
 * Original author: Edmund Wagner
 * Creation date: 21.06.2007
 *
 * Source: $HeadURL$
 * Last changed: $LastChangedDate$
 * 
 * the unrar licence applies to all junrar source and binary distributions 
 * you are not allowed to use this source to re-create the RAR compression algorithm
 * 
 * Here some html entities which can be used for escaping javadoc tags:
 * "&":  "&#038;" or "&amp;"
 * "<":  "&#060;" or "&lt;"
 * ">":  "&#062;" or "&gt;"
 * "@":  "&#064;" 
 */
package com.github.junrar.unpack;

import java.io.IOException;
import java.util.Arrays;

import com.github.junrar.exception.RarException;
import com.github.junrar.unpack.decode.AudioVariables;
import com.github.junrar.unpack.decode.BitDecode;
import com.github.junrar.unpack.decode.Compress;
import com.github.junrar.unpack.decode.Decode;
import com.github.junrar.unpack.decode.DistDecode;
import com.github.junrar.unpack.decode.LitDecode;
import com.github.junrar.unpack.decode.LowDistDecode;
import com.github.junrar.unpack.decode.MultDecode;
import com.github.junrar.unpack.decode.RepDecode;

/**
 * DOCUMENT ME
 * 
 * @author $LastChangedBy$
 * @version $LastChangedRevision$
 */
public abstract class Unpack20 extends Unpack15
 {

  protected MultDecode[] MD = new MultDecode[4];

  protected byte[] UnpOldTable20 = new byte[Compress.MC20 * 4];

  protected int UnpAudioBlock, UnpChannels, UnpCurChannel, UnpChannelDelta;

  protected AudioVariables[] AudV = new AudioVariables[4];

  protected LitDecode LD = new LitDecode();

  protected DistDecode DD = new DistDecode();

  protected LowDistDecode LDD = new LowDistDecode();

  protected RepDecode RD = new RepDecode();

  protected BitDecode BD = new BitDecode();

  public static final int[] LDecode = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80,
                                       96, 112, 128, 160, 192, 224};

  public static final byte[] LBits = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5,
                                      5};

  public static final int[] DDecode = {0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768,
                                       1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152,
                                       65536, 98304, 131072, 196608, 262144, 327680, 393216, 458752, 524288, 589824,
                                       655360, 720896, 786432, 851968, 917504, 983040};

  public static final int[] DBits = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
                                     12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
                                     16, 16};

  public static final int[] SDDecode = {0, 4, 8, 16, 32, 64, 128, 192};

  public static final int[] SDBits = {2, 2, 3, 4, 5, 6, 6, 6};

  protected void unpack20(boolean solid) throws IOException, RarException
   {

    int Bits;

    if (suspended)
     {
      unpPtr = wrPtr;
     }
    else
     {
      unpInitData(solid);
      if (!unpReadBuf())
       {
        return;
       }
      if (!solid)
       {
        if (!ReadTables20())
         {
          return;
         }
       }
      --destUnpSize;
     }

    while (destUnpSize >= 0)
     {
      unpPtr &= Compress.MAXWINMASK;

      if (inAddr > readTop - 30)
       if (!unpReadBuf())
        break;
      if (((wrPtr - unpPtr) & Compress.MAXWINMASK) < 270 && wrPtr != unpPtr)
       {
        oldUnpWriteBuf();
        if (suspended)
         return;
       }
      if (UnpAudioBlock != 0)
       {
        int AudioNumber = decodeNumber(MD[UnpCurChannel]);

        if (AudioNumber == 256)
         {
          if (!ReadTables20())
           break;
          continue;
         }
        window[unpPtr++] = DecodeAudio(AudioNumber);
        if (++UnpCurChannel == UnpChannels)
         UnpCurChannel = 0;
        --destUnpSize;
        continue;
       }

      int Number = decodeNumber(LD);
      if (Number < 256)
       {
        window[unpPtr++] = (byte) Number;
        --destUnpSize;
        continue;
       }
      if (Number > 269)
       {
        int Length = LDecode[Number -= 270] + 3;
        if ((Bits = LBits[Number]) > 0)
         {
          Length += getbits() >>> (16 - Bits);
          addbits(Bits);
         }

        int DistNumber = decodeNumber(DD);
        int Distance = DDecode[DistNumber] + 1;
        if ((Bits = DBits[DistNumber]) > 0)
         {
          Distance += getbits() >>> (16 - Bits);
          addbits(Bits);
         }

        if (Distance >= 0x2000)
         {
          Length++;
          if (Distance >= 0x40000L)
           Length++;
         }

        CopyString20(Length, Distance);
        continue;
       }
      if (Number == 269)
       {
        if (!ReadTables20())
         break;
        continue;
       }
      if (Number == 256)
       {
        CopyString20(lastLength, lastDist);
        continue;
       }
      if (Number < 261)
       {
        int Distance = oldDist[(oldDistPtr - (Number - 256)) & 3];
        int LengthNumber = decodeNumber(RD);
        int Length = LDecode[LengthNumber] + 2;
        if ((Bits = LBits[LengthNumber]) > 0)
         {
          Length += getbits() >>> (16 - Bits);
          addbits(Bits);
         }
        if (Distance >= 0x101)
         {
          Length++;
          if (Distance >= 0x2000)
           {
            Length++;
            if (Distance >= 0x40000)
             Length++;
           }
         }
        CopyString20(Length, Distance);
        continue;
       }
      if (Number < 270)
       {
        int Distance = SDDecode[Number -= 261] + 1;
        if ((Bits = SDBits[Number]) > 0)
         {
          Distance += getbits() >>> (16 - Bits);
          addbits(Bits);
         }
        CopyString20(2, Distance);
        continue;
       }
     }
    ReadLastTables();
    oldUnpWriteBuf();

   }

  protected void CopyString20(int Length, int Distance)
   {
    lastDist = oldDist[oldDistPtr++ & 3] = Distance;
    lastLength = Length;
    destUnpSize -= Length;

    int DestPtr = unpPtr - Distance;
    if (DestPtr < Compress.MAXWINSIZE - 300 && unpPtr < Compress.MAXWINSIZE - 300)
     {
      window[unpPtr++] = window[DestPtr++];
      window[unpPtr++] = window[DestPtr++];
      while (Length > 2)
       {
        Length--;
        window[unpPtr++] = window[DestPtr++];
       }
     }
    else
     {
      while ((Length--) != 0)
       {
        window[unpPtr] = window[DestPtr++ & Compress.MAXWINMASK];
        unpPtr = (unpPtr + 1) & Compress.MAXWINMASK;
       }
     }
   }

  protected void makeDecodeTables(byte[] lenTab, int offset, Decode dec, int size)
   {
    int[] lenCount = new int[16];
    int[] tmpPos = new int[16];
    int i;
    long M, N;

    Arrays.fill(lenCount, 0);// memset(LenCount,0,sizeof(LenCount));

    Arrays.fill(dec.getDecodeNum(), 0);// memset(Dec->DecodeNum,0,Size*sizeof(*Dec->DecodeNum));

    for (i = 0; i < size; i++)
     {
      lenCount[(int) (lenTab[offset + i] & 0xF)]++;
     }
    lenCount[0] = 0;
    for (tmpPos[0] = 0, dec.getDecodePos()[0] = 0, dec.getDecodeLen()[0] = 0, N = 0, i = 1; i < 16; i++)
     {
      N = 2 * (N + lenCount[i]);
      M = N << (15 - i);
      if (M > 0xFFFF)
       {
        M = 0xFFFF;
       }
      dec.getDecodeLen()[i] = (int) M;
      tmpPos[i] = dec.getDecodePos()[i] = dec.getDecodePos()[i - 1] + lenCount[i - 1];
     }

    for (i = 0; i < size; i++)
     {
      if (lenTab[offset + i] != 0)
       {
        dec.getDecodeNum()[tmpPos[lenTab[offset + i] & 0xF]++] = i;
       }
     }
    dec.setMaxNum(size);
   }

  protected int decodeNumber(Decode dec)
   {
    int bits;
    long bitField = getbits() & 0xfffe;
    // if (bitField < dec.getDecodeLen()[8]) {
    // if (bitField < dec.getDecodeLen()[4]) {
    // if (bitField < dec.getDecodeLen()[2]) {
    // if (bitField < dec.getDecodeLen()[1]) {
    // bits = 1;
    // } else {
    // bits = 2;
    // }
    // } else {
    // if (bitField < dec.getDecodeLen()[3]) {
    // bits = 3;
    // } else {
    // bits = 4;
    // }
    // }
    // } else {
    // if (bitField < dec.getDecodeLen()[6]) {
    // if (bitField < dec.getDecodeLen()[5])
    // bits = 5;
    // else
    // bits = 6;
    // } else {
    // if (bitField < dec.getDecodeLen()[7]) {
    // bits = 7;
    // } else {
    // bits = 8;
    // }
    // }
    // }
    // } else {
    // if (bitField < dec.getDecodeLen()[12]) {
    // if (bitField < dec.getDecodeLen()[10])
    // if (bitField < dec.getDecodeLen()[9])
    // bits = 9;
    // else
    // bits = 10;
    // else if (bitField < dec.getDecodeLen()[11])
    // bits = 11;
    // else
    // bits = 12;
    // } else {
    // if (bitField < dec.getDecodeLen()[14]) {
    // if (bitField < dec.getDecodeLen()[13]) {
    // bits = 13;
    // } else {
    // bits = 14;
    // }
    // } else {
    // bits = 15;
    // }
    // }
    // }
    // addbits(bits);
    // int N = dec.getDecodePos()[bits]
    // + (((int) bitField - dec.getDecodeLen()[bits - 1]) >>> (16 - bits));
    // if (N >= dec.getMaxNum()) {
    // N = 0;
    // }
    // return (dec.getDecodeNum()[N]);
    int[] decodeLen = dec.getDecodeLen();
    if (bitField < decodeLen[8])
     {
      if (bitField < decodeLen[4])
       {
        if (bitField < decodeLen[2])
         {
          if (bitField < decodeLen[1])
           {
            bits = 1;
           }
          else
           {
            bits = 2;
           }
         }
        else
         {
          if (bitField < decodeLen[3])
           {
            bits = 3;
           }
          else
           {
            bits = 4;
           }
         }
       }
      else
       {
        if (bitField < decodeLen[6])
         {
          if (bitField < decodeLen[5])
           bits = 5;
          else
           bits = 6;
         }
        else
         {
          if (bitField < decodeLen[7])
           {
            bits = 7;
           }
          else
           {
            bits = 8;
           }
         }
       }
     }
    else
     {
      if (bitField < decodeLen[12])
       {
        if (bitField < decodeLen[10])
         if (bitField < decodeLen[9])
          bits = 9;
         else
          bits = 10;
        else if (bitField < decodeLen[11])
         bits = 11;
        else
         bits = 12;
       }
      else
       {
        if (bitField < decodeLen[14])
         {
          if (bitField < decodeLen[13])
           {
            bits = 13;
           }
          else
           {
            bits = 14;
           }
         }
        else
         {
          bits = 15;
         }
       }
     }
    addbits(bits);
    int N = dec.getDecodePos()[bits] + (((int) bitField - decodeLen[bits - 1]) >>> (16 - bits));
    if (N >= dec.getMaxNum())
     {
      N = 0;
     }
    return (dec.getDecodeNum()[N]);
   }

  protected boolean ReadTables20() throws IOException, RarException
   {
    byte[] BitLength = new byte[Compress.BC20];
    byte[] Table = new byte[Compress.MC20 * 4];
    int TableSize, N, I;
    if (inAddr > readTop - 25)
     {
      if (!unpReadBuf())
       {
        return (false);
       }
     }
    int BitField = getbits();
    UnpAudioBlock = (BitField & 0x8000);

    if (0 == (BitField & 0x4000))
     {
      // memset(UnpOldTable20,0,sizeof(UnpOldTable20));
      Arrays.fill(UnpOldTable20, (byte) 0);
     }
    addbits(2);

    if (UnpAudioBlock != 0)
     {
      UnpChannels = ((BitField >>> 12) & 3) + 1;
      if (UnpCurChannel >= UnpChannels)
       {
        UnpCurChannel = 0;
       }
      addbits(2);
      TableSize = Compress.MC20 * UnpChannels;
     }
    else
     {
      TableSize = Compress.NC20 + Compress.DC20 + Compress.RC20;
     }
    for (I = 0; I < Compress.BC20; I++)
     {
      BitLength[I] = (byte) (getbits() >>> 12);
      addbits(4);
     }
    makeDecodeTables(BitLength, 0, BD, Compress.BC20);
    I = 0;
    while (I < TableSize)
     {
      if (inAddr > readTop - 5)
       {
        if (!unpReadBuf())
         {
          return (false);
         }
       }
      int Number = decodeNumber(BD);
      if (Number < 16)
       {
        Table[I] = (byte) ((Number + UnpOldTable20[I]) & 0xf);
        I++;
       }
      else if (Number == 16)
       {
        N = (getbits() >>> 14) + 3;
        addbits(2);
        while (N-- > 0 && I < TableSize)
         {
          Table[I] = Table[I - 1];
          I++;
         }
       }
      else
       {
        if (Number == 17)
         {
          N = (getbits() >>> 13) + 3;
          addbits(3);
         }
        else
         {
          N = (getbits() >>> 9) + 11;
          addbits(7);
         }
        while (N-- > 0 && I < TableSize)
         Table[I++] = 0;
       }
     }
    if (inAddr > readTop)
     {
      return (true);
     }
    if (UnpAudioBlock != 0)
     for (I = 0; I < UnpChannels; I++)
      makeDecodeTables(Table, I * Compress.MC20, MD[I], Compress.MC20);
    else
     {
      makeDecodeTables(Table, 0, LD, Compress.NC20);
      makeDecodeTables(Table, Compress.NC20, DD, Compress.DC20);
      makeDecodeTables(Table, Compress.NC20 + Compress.DC20, RD, Compress.RC20);
     }
    // memcpy(UnpOldTable20,Table,sizeof(UnpOldTable20));
    for (int i = 0; i < UnpOldTable20.length; i++)
     {
      UnpOldTable20[i] = Table[i];
     }
    return (true);
   }

  protected void unpInitData20(boolean Solid)
   {
    if (!Solid)
     {
      UnpChannelDelta = UnpCurChannel = 0;
      UnpChannels = 1;
      // memset(AudV,0,sizeof(AudV));
      Arrays.fill(AudV, new AudioVariables());
      // memset(UnpOldTable20,0,sizeof(UnpOldTable20));
      Arrays.fill(UnpOldTable20, (byte) 0);
     }
   }

  protected void ReadLastTables() throws IOException, RarException
   {
    if (readTop >= inAddr + 5)
     {
      if (UnpAudioBlock != 0)
       {
        if (decodeNumber(MD[UnpCurChannel]) == 256)
         {
          ReadTables20();
         }
       }
      else
       {
        if (decodeNumber(LD) == 269)
         {
          ReadTables20();
         }
       }
     }
   }

  protected byte DecodeAudio(int Delta)
   {
    AudioVariables v = AudV[UnpCurChannel];
    v.setByteCount(v.getByteCount() + 1);
    v.setD4(v.getD3());
    v.setD3(v.getD2());// ->D3=V->D2;
    v.setD2(v.getLastDelta() - v.getD1());// ->D2=V->LastDelta-V->D1;
    v.setD1(v.getLastDelta());// V->D1=V->LastDelta;
    // int PCh=8*V->LastChar+V->K1*V->D1 +V->K2*V->D2 +V->K3*V->D3
    // +V->K4*V->D4+ V->K5*UnpChannelDelta;
    int PCh = 8 * v.getLastChar() + v.getK1() * v.getD1();
    PCh += v.getK2() * v.getD2() + v.getK3() * v.getD3();
    PCh += v.getK4() * v.getD4() + v.getK5() * UnpChannelDelta;
    PCh = (PCh >>> 3) & 0xFF;

    int Ch = PCh - Delta;

    int D = ((byte) Delta) << 3;

    v.getDif()[0] += Math.abs(D);// V->Dif[0]+=abs(D);
    v.getDif()[1] += Math.abs(D - v.getD1());// V->Dif[1]+=abs(D-V->D1);
    v.getDif()[2] += Math.abs(D + v.getD1());// V->Dif[2]+=abs(D+V->D1);
    v.getDif()[3] += Math.abs(D - v.getD2());// V->Dif[3]+=abs(D-V->D2);
    v.getDif()[4] += Math.abs(D + v.getD2());// V->Dif[4]+=abs(D+V->D2);
    v.getDif()[5] += Math.abs(D - v.getD3());// V->Dif[5]+=abs(D-V->D3);
    v.getDif()[6] += Math.abs(D + v.getD3());// V->Dif[6]+=abs(D+V->D3);
    v.getDif()[7] += Math.abs(D - v.getD4());// V->Dif[7]+=abs(D-V->D4);
    v.getDif()[8] += Math.abs(D + v.getD4());// V->Dif[8]+=abs(D+V->D4);
    v.getDif()[9] += Math.abs(D - UnpChannelDelta);// V->Dif[9]+=abs(D-UnpChannelDelta);
    v.getDif()[10] += Math.abs(D + UnpChannelDelta);// V->Dif[10]+=abs(D+UnpChannelDelta);

    v.setLastDelta((byte) (Ch - v.getLastChar()));
    UnpChannelDelta = v.getLastDelta();
    v.setLastChar(Ch);// V->LastChar=Ch;

    if ((v.getByteCount() & 0x1F) == 0)
     {
      int MinDif = v.getDif()[0], NumMinDif = 0;
      v.getDif()[0] = 0;// ->Dif[0]=0;
      for (int I = 1; I < v.getDif().length; I++)
       {
        if (v.getDif()[I] < MinDif)
         {
          MinDif = v.getDif()[I];
          NumMinDif = I;
         }
        v.getDif()[I] = 0;
       }
      switch (NumMinDif)
       {
       case 1:
        if (v.getK1() >= -16)
         {
          v.setK1(v.getK1() - 1);// V->K1--;
         }
        break;
       case 2:
        if (v.getK1() < 16)
         {
          v.setK1(v.getK1() + 1);// V->K1++;
         }
        break;
       case 3:
        if (v.getK2() >= -16)
         {
          v.setK2(v.getK2() - 1);// V->K2--;
         }
        break;
       case 4:
        if (v.getK2() < 16)
         {
          v.setK2(v.getK2() + 1);// V->K2++;
         }
        break;
       case 5:
        if (v.getK3() >= -16)
         {
          v.setK3(v.getK3() - 1);
         }
        break;
       case 6:
        if (v.getK3() < 16)
         {
          v.setK3(v.getK3() + 1);
         }
        break;
       case 7:
        if (v.getK4() >= -16)
         {
          v.setK4(v.getK4() - 1);
         }
        break;
       case 8:
        if (v.getK4() < 16)
         {
          v.setK4(v.getK4() + 1);
         }
        break;
       case 9:
        if (v.getK5() >= -16)
         {
          v.setK5(v.getK5() - 1);
         }
        break;
       case 10:
        if (v.getK5() < 16)
         {
          v.setK5(v.getK5() + 1);
         }
        break;
       }
     }
    return ((byte) Ch);
   }

 }
